/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package sirtet;

import java.util.HashSet;
import sirtet.event.PlayerActionEvent;
import sirtet.event.PlayerListener;
import java.util.Set;
import sirtet.event.PlayerEvent;

/**
 * An abstract player that can trigger actions
 * @author guangwei.zhu
 */
public abstract class Player {

    protected Set<PlayerListener> actionListeners = new HashSet<PlayerListener>();
    private GameBoard bindingBoard;
    private final String playerID;
    private final String gametype;
    private final boolean ismain;
    private boolean delayRemoval = false;
    private Set<PlayerListener> toBeRemoved = new HashSet<PlayerListener>();
    private boolean isReady = false;

    protected Player(Player innerPlayer) {
        this(innerPlayer.id(), innerPlayer.gameType(), innerPlayer.isMain());
    }

    public Player(String playerID, String gametype, boolean isMain) {
        if (playerID.contains("\n") || playerID.contains(":")) {
            throw new IllegalArgumentException();
        }

        this.playerID = playerID;
        this.gametype = gametype;
        this.ismain = isMain;
    }

    /**
     * Return a boolean value indicating whether this player is the main player
     * in the game (not an NPC or assistant role).
     * @return if this instance represents a main player
     */
    public boolean isMain() {
        return this.ismain;
    }

    /**
     * Return the string that indicates the GameType
     * @return the gametype string (e.g. Tetris for TetrisPlayer)
     */
    public String gameType() {
        return this.gametype;
    }

    /**
     * Add an action listener to the event handler set
     * @param listener an object whose class implements PlayerListener
     */
    public void addPlayerListener(PlayerListener listener) {
        actionListeners.add(listener);
    }

    /**
     * Remove an action listener from the event handler set
     * @param listener an object whose class implements PlayerListener
     */
    public void removePlayerListener(PlayerListener listener) {
        if (delayRemoval) {
            toBeRemoved.add(listener);
        } else {
            actionListeners.remove(listener);
        }
    }

    private synchronized void delayRemove() {
        if (toBeRemoved.size() > 0) {
            actionListeners.removeAll(toBeRemoved);
            toBeRemoved.clear();
            delayRemoval = false;
        }
    }

    /**
     * Get the player ID
     * @return a string representing the player ID
     */
    public String id() {
        return this.playerID;
    }

    /**
     * Query if the player is ready for action
     * @return true if the player is ready to perform actions
     */
    public boolean ready() {
        return isReady;
    }

    /**
     * Set the status of readiness.  If the status is set to true, a PlayerReady
     * event will be raised.
     * @param ready true if this player is ready to start the game
     */
    public void setReady(boolean ready) {
        isReady = ready;
        if (ready) {
            notifyReady();
        }
    }

    /**
     * Call all listeners with the triggered action
     * @param action player-triggered action
     * @return true if the action is invoked; false if the player is disabled
     * or some policy is preventing the player from performing the action
     */
    protected boolean invokeAction(GameAction action) {
        if (!enabled) {
            return false; // Disabled state
        }
        delayRemoval = true;
        for (PlayerListener listener : actionListeners) {
            listener.actionPerformed(new PlayerActionEvent(this, action));
        }
        delayRemove();
        return true;
    }

    /**
     * Notify all listeners that the enabled state is changed
     * @param enabled the current Enabled state
     */
    protected void notifyEnablingChanged(boolean enabled) {
        delayRemoval = true;

        for (PlayerListener listener : actionListeners) {
            if (enabled) {
                listener.playerEnabled(new PlayerEvent(this));
            } else {
                listener.playerDisabled(new PlayerEvent(this));
            }
        }
        delayRemove();
    }

    /**
     * Call all listeners with the Ready message
     */
    protected void notifyReady() {
        delayRemoval = true;
        for (PlayerListener listener : actionListeners) {
            listener.playerReady(new PlayerEvent(this));
        }
        delayRemove();
    }

    /**
     * Raise the signal to notify the game board about quitting
     */
    protected void quit() {
        delayRemoval = true;
        for (PlayerListener listener : actionListeners) {
            listener.playerQuit(new PlayerEvent(this));
        }
        delayRemove();
    }

    /**
     * An interface method called by a GameBoard object automatically when
     * the player is registered at that board.  This method should not be called
     * manually.
     * @param board the GameBoard object to which this player is bound (i.e. registered)
     */
    public void bindBoard(GameBoard board) {
        if (bindingBoard == null) {
            bindingBoard = board;
        } else if (board != bindingBoard) {
            throw new IllegalMonitorStateException("This player is already registered in another board.");
        }
    }

    /**
     * An interface method called by a GameBoard object automatically when
     * the player is unregistered at that board.  This method should not be called
     * manually.
     * @param board the GameBoard object to which this player was bound (i.e. registered)
     */
    public void unbindBoard(GameBoard board) {
        if (board == bindingBoard) {
            bindingBoard = null;
        }
    }

    /**
     * Return the GameBoard object in which this player has joined.
     * @return the GameBoard in which this player has joined
     */
    protected GameBoard getBoard() {
        return this.bindingBoard;
    }

    /**
     * Allow or disallow actions triggered by this player.  A PlayerEnabled or PlayerDisabled
     * event will be raised upon changing the enabling status.
     * @param enabled true if actions from this player is allowed.
     */
    public void setEnabled(boolean enabled) {
        boolean oldEnabled = this.enabled;
        this.enabled = enabled;
        if (oldEnabled != enabled) {    // State changed
            notifyEnablingChanged(enabled);
        }
    }

    public boolean getEnabled() {
        return enabled;
    }
    private boolean enabled;
}
